// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <memory>
#include <sstream>
#include <string>
#include <vector>

#include "coin2/exchange/base/symbology/derivative_util.h"
#include "coin2/exchange/base/symbology/product.h"

namespace coin2::exchange::base::symbology {

template <typename CurrencyT, typename ProductT>
class FuturesProduct : public CurrencyPairProduct<CurrencyT> {
 public:
  FuturesProduct(
      const CurrencyT& base,
      const CurrencyT& quote,
      const std::string& native_product,
      const std::string& native_subscription_symbol,
      const ExchangeType exchange,
      const coin::proto::ContractType contract_type,
      const int64_t expiry_timestamp,
      const int64_t resolve_timestamp)
      : CurrencyPairProduct<CurrencyT>(
            base,
            quote,
            ConvertToFuturesNormStr(base, quote, expiry_timestamp),
            native_product,
            ConvertToFuturesSubscriptionSymbol(base, quote, contract_type),
            native_subscription_symbol,
            MarketType::Futures,
            exchange),
        contract_type_(contract_type),
        expiry_timestamp_(expiry_timestamp),
        resolve_timestamp_(resolve_timestamp),
        expiry_str_(ConvertToExpiryStr(expiry_timestamp_)) {}

 public:
  int64_t expiry() const { return expiry_timestamp_; }

  const std::string& expiry_str() const { return expiry_str_; }

  coin::proto::ContractType contract_type() const { return contract_type_; }

  const std::string& contract_type_name() const { return ContractTypeToString(contract_type_); }

  int64_t resolve_timestamp() const { return resolve_timestamp_; }

  static std::unique_ptr<ProductT> FromNormStrImpl(
      const std::string& product_str,
      const int64_t resolve_timestamp) {
    std::string base_str;
    std::string quote_str;
    std::string expiry_str;
    bool success;
    success = ParseFuturesNormStr(product_str, &base_str, &quote_str, &expiry_str);

    if (success) {
      auto base = CurrencyT::FromNormStrNoThrow(base_str, resolve_timestamp);
      auto quote = CurrencyT::FromNormStrNoThrow(quote_str, resolve_timestamp);

      if (base && quote) {
        std::string native_product;
        std::string native_subscription_symbol;
        coin::proto::ContractType contract_type;
        int64_t expiry_timestamp;

        success = ProductT::CalculateParams(
            *base,
            *quote,
            expiry_str,
            resolve_timestamp,
            &native_product,
            &native_subscription_symbol,
            &contract_type,
            &expiry_timestamp);

        if (success) {
          return std::make_unique<ProductT>(
              *base,
              *quote,
              native_product,
              native_subscription_symbol,
              contract_type,
              expiry_timestamp,
              resolve_timestamp);
        }
      }
    }
    return {};
  }

  IProduct* Clone() const final {
    return new ProductT(
        CurrencyPairProduct<CurrencyT>::base_impl(),
        CurrencyPairProduct<CurrencyT>::quote_impl(),
        CurrencyPairProduct<CurrencyT>::native_product(),
        CurrencyPairProduct<CurrencyT>::feed_native_symbol(),
        contract_type(),
        expiry_timestamp_,
        resolve_timestamp_);
  }

 private:
  static bool ParseFuturesNormStr(
      const std::string& futures_norm_str,
      std::string* base,
      std::string* quote,
      std::string* expiry_str) {
    std::vector<std::string> out;
    out = Split(futures_norm_str, "-.");
    if (out.size() == 3 || out.size() == 4) {
      *base = out.at(0);
      *quote = out.at(1);
      *expiry_str = out.at(2);
      return true;
    }
    return false;
  }

  static std::string ConvertToFuturesNormStr(
      const ICurrency& base,
      const ICurrency& quote,
      const int64_t expiry_timestamp) {
    std::stringstream ss;
    ss << base.currency() << "-" << quote.currency() << "." << ConvertToExpiryStr(expiry_timestamp);
    return ss.str();
  }

  static std::string ConvertToFuturesSubscriptionSymbol(
      const ICurrency& base,
      const ICurrency& quote,
      const coin::proto::ContractType contract_type) {
    std::stringstream ss;
    ss << base.currency() << "-" << quote.currency() << "." << ContractTypeToString(contract_type);
    return ss.str();
  }

  static std::string ConvertToExpiryStr(int64_t expiry_timestamp) {
    if (expiry_timestamp == PERPETUAL_TIMESTAMP) {
      return "PERPETUAL";
    } else if (expiry_timestamp == IMMEDIATE_TIMESTAMP) {
      return "IMMEDIATE";
    } else {
      return ToDateStr(expiry_timestamp);
    }
  }

 public:
  static const int64_t PERPETUAL_TIMESTAMP = 4102416000LL * 1000000000LL;
  static const int64_t IMMEDIATE_TIMESTAMP = 0;

 private:
  const coin::proto::ContractType contract_type_;
  const int64_t expiry_timestamp_;
  const int64_t resolve_timestamp_;
  const std::string expiry_str_;
};

}  // namespace coin2::exchange::base::symbology
